package com.ruyuan.dfs.client;

import com.ruyuan.dfs.server.BaseChannelInitializer;
import com.ruyuan.dfs.server.ClientBaseChannelInitializer;
import com.ruyuan.dfs.server.NettyPacket;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.ResourceLeakDetector;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @description:
 * @Author gckj
 * @Email:
 * @Date: 2024/8/2 15:10
 * @Version 1.0
 */
@Slf4j
public class NetClient {


    private DefaultScheduler defaultScheduler;

    private DefaultChannelHandler defaultChannelHandler;

    private EventLoopGroup connectThreadGroup;

    private ClientBaseChannelInitializer baseChannelInitializer;

    private AtomicBoolean started = new AtomicBoolean(true);

    private List<NetClientFailListener> netClientFailListeners = new ArrayList<>();


    public NetClient(DefaultScheduler defaultScheduler) {
        this.defaultScheduler = defaultScheduler;
        this.connectThreadGroup = new NioEventLoopGroup(1);
        this.defaultChannelHandler = new DefaultChannelHandler();
        this.defaultChannelHandler.addConnectListener(connected -> {
            if (connected) {
                synchronized (NetClient.this) {
                    NetClient.this.notifyAll();
                }
            }
        });
        this.baseChannelInitializer = new ClientBaseChannelInitializer(defaultChannelHandler);
    }

    public SocketChannel socketChannel() {
        return defaultChannelHandler.socketChannel();
    }

    public void addConnectListener(ConnectListener listener) {
        defaultChannelHandler.addConnectListener(listener);
    }

    public void addNettyPackageListener(NettyPacketListener listener) {
        defaultChannelHandler.addNettyPackageListener(listener);
    }

    /**
     * 添加连接失败监听器
     *
     * @param listener 连接失败
     */
    public void addNetClientFailListener(NetClientFailListener listener) {
        netClientFailListeners.add(listener);
    }


    public boolean isConnected() {
        return defaultChannelHandler.isConnected();
    }

    public void ensureConnected(int timeout) throws InterruptedException {
        int remainTimeout = timeout;
        synchronized (this) {
            while (!isConnected()) {
                if (!started.get()) {
                    throw new InterruptedException("无法连接上服务器：");
                }
                if (timeout > 0) {
                    if (remainTimeout <= 0) {
                        throw new InterruptedException("无法连接上服务器：");
                    }
                    wait(10);
                    remainTimeout -= 10;
                } else {
                    wait(10);
                }
            }
        }
    }

    /**
     * 启动连接
     *
     * @param hostname 主机名
     * @param port     端口
     */
    public void connect(String hostname, int port) {
        connect(hostname, port, 1, 0);
    }

    private void connect(String hostname, int port, final int connectTimes, int delay) {
        defaultScheduler.scheduleOnce("服务端连接", () -> {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(connectThreadGroup)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                    .channel(NioSocketChannel.class)
                    .handler(baseChannelInitializer);
            ResourceLeakDetector.setLevel(ResourceLeakDetector.Level.ADVANCED);
            try {
                ChannelFuture channelFuture = bootstrap.connect(hostname, port).sync();
                channelFuture.channel().closeFuture().addListener((ChannelFutureListener) f -> f.channel().close());
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                log.info("发起连接后同步等待连接被打断");
            } catch (Exception e) {
                log.error("发起连接过程中出现异常：[ex={}, started={}", e.getMessage(), started.get());
            } finally {
                int curConnectTimes = connectTimes + 1;
                maybeRetry(hostname, port, curConnectTimes);
            }
        }, delay);

    }

    private int retryTime = 2;

    /**
     * 尝试重新发起连接
     *
     * @param hostname     主机名
     * @param port         端口号
     * @param connectTimes 当前重试次数
     */
    private void maybeRetry(String hostname, int port, int connectTimes) {
        if (started.get()) {
            boolean retry = retryTime < 0 || connectTimes <= retryTime;
            if (retry) {
                log.error("重新发起连接：[started={}, name={}]", started.get());
                connect(hostname, port, connectTimes, 3000);
//            } else {
//                shutdown();
//                log.info("重试次数超出阈值，不再进行重试：[retryTime={}]", retryTime);
//                for (NetClientFailListener listener : new ArrayList<>(netClientFailListeners)) {
//                    try {
//                        listener.onConnectFail();
//                    } catch (Exception e) {
//                        log.error("Exception occur on invoke listener :", e);
//                    }
//                }
//            }
            }
        }

    }

    public void send(NettyPacket nettyPacket) throws InterruptedException {
        ensureConnected(-1);
        defaultChannelHandler.send(nettyPacket);
    }


    public void shutdown() {
        if(log.isDebugEnabled()) {
            log.debug("Shutdown NetClient : [name={}]", "s");
        }
        started.set(false);
        if (connectThreadGroup != null) {
            connectThreadGroup.shutdownGracefully();
        }
        defaultChannelHandler.clearConnectListener();
        defaultChannelHandler.clearNettyPackageListener();
    }
}
